using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using VncSharp;
using VncSharp.Encodings;
using System.Drawing;

using System.ComponentModel;
using System.Drawing.Imaging;
/// <summary>
/// Summary description for VncWeb
/// </summary>

public delegate void ConnectCompleteHandler(object sender, ConnectEventArgs e);
public delegate string AuthenticateDelegate();

#region Enums
public enum SpecialKeys
{
    CtrlAltDel,
    AltF4,
    CtrlEsc,
    Ctrl,
    Alt
}
public enum RuntimeState
{
    Disconnected,
    Disconnecting,
    Connected,
    Connecting
}
#endregion 


public class VncWeb:WebControl,IDisposable
{
    public event ConnectCompleteHandler ConnectComplete;
    public event EventHandler ConnectionLost;
    public AuthenticateDelegate GetPassword;

    public Bitmap desktop;						// Internal representation of remote image.
    System.Drawing.Image designModeDesktop;			// Used when painting control in VS.NET designer
    VncClient vnc;						// The Client object handling all protocol-level interaction
    int port = 5900;					// The port to connect to on remote host (5900 is default)
    bool passwordPending = false;		// After Connect() is called, a password might be required.
    bool fullScreenRefresh = true;		// Whether or not to request the entire remote screen be sent.
    RuntimeState state = RuntimeState.Disconnected;
    string _password = string.Empty;
    private Size _ClientSize;
    #region Properties
    public Size ClientSize
    {
        get { return _ClientSize; }
        set { _ClientSize = value; }
    }
    [DefaultValue(5900)]
    [Description("The port number used by the VNC Host (typically 5900)")]
    /// <summary>
    /// The port number used by the VNC Host (typically 5900).
    /// </summary>
    public int VncPort
    {
        get
        {
            return port;
        }
        set
        {
            // Ignore attempts to use invalid port numbers
            if (value < 1 | value > 65535) value = 5900;
            port = value;
        }
    }
    public string Password
    { 
        get { return _password; }
        set { _password = value; }
    }
    /// <summary>
    /// True if the RemoteDesktop is connected and authenticated (if necessary) with a remote VNC Host; otherwise False.
    /// </summary>
    public bool IsConnected
    {
        get
        {
            return state == RuntimeState.Connected;
        }
    }
    /// <summary>
    /// Returns a more appropriate default size for initial drawing of the control at design time
    /// </summary>
    protected Size DefaultSize
    {
        get
        {
            return new Size(400, 200);
        }
    }
    [Description("The name of the remote desktop.")]
    /// <summary>
    /// The name of the remote desktop, or "Disconnected" if not connected.
    /// </summary>
    public string Hostname
    {
        get
        {
            return vnc == null ? "Disconnected" : vnc.HostName;
        }
    }
    /// <summary>
    /// Get a complete update of the entire screen from the remote host.
    /// </summary>
    /// <remarks>You should allow users to call FullScreenUpdate in order to correct
    /// corruption of the local image.  This will simply request that the next update be
    /// for the full screen, and not a portion of it.  It will not do the update while
    /// blocking.
    /// </remarks>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is not in the Connected state.  See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    public void FullScreenUpdate()
    {
        InsureConnection(true);
        fullScreenRefresh = true;
    }
    #endregion

    #region Private Methods
    /// <summary>
    /// Insures the state of the connection to the server, either Connected or Not Connected depending on the value of the connected argument.
    /// </summary>
    /// <param name="connected">True if the connection must be established, otherwise False.</param>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is in the wrong state.</exception>
    private void InsureConnection(bool connected)
    {
        // Grab the name of the calling routine:
        string methodName = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name;

        if (connected)
        {
            System.Diagnostics.Debug.Assert(state == RuntimeState.Connected ||
                                            state == RuntimeState.Disconnecting, // special case for Disconnect()
                                            string.Format("RemoteDesktop must be in RuntimeState.Connected before calling {0}.", methodName));
            if (state != RuntimeState.Connected)
            {
                throw new InvalidOperationException("RemoteDesktop must be in Connected state before calling methods that require an established connection.");
            }
        }
        else
        { // disconnected
            System.Diagnostics.Debug.Assert(state == RuntimeState.Disconnected,
                                            string.Format("RemoteDesktop must be in RuntimeState.Disconnected before calling {0}.", methodName));
            if (state != RuntimeState.Disconnected)
            {
                throw new InvalidOperationException("RemoteDesktop cannot be in Connected state when calling methods that establish a connection.");
            }
        }
    }

    #endregion
    public void update()
    {
        vnc.StartUpdates();
    }
    #region Event Handlers
    // This event handler deals with Frambebuffer Updates coming from the host. An
    // EncodedRectangle object is passed via the VncEventArgs (actually an IDesktopUpdater
    // object so that *only* Draw() can be called here--Decode() is done elsewhere).
    // The VncClient object handles thread marshalling onto the UI thread.
    protected void VncUpdate(object sender, VncEventArgs e)
    {
        try
        {
            e.DesktopUpdater.Draw(desktop);
            if (state == RuntimeState.Connected)
            {
                vnc.RequestScreenUpdate(fullScreenRefresh);
                //desktop.Save("C:\\temp\\" + DateTime.Now.Millisecond.ToString() + DateTime.Now.Ticks.ToString() + ".gif", ImageFormat.Gif);
                // Make sure the next screen update is incremental
                DrawDesktopImage(desktop, null);
                //HttpContext.Current.Session["img"] = desktop;
                fullScreenRefresh = false;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        
      
        
    }
    /// <summary>
    /// RemoteDesktop listens for ConnectionLost events from the VncClient object.
    /// </summary>
    /// <param name="sender">The VncClient object that raised the event.</param>
    /// <param name="e">An empty EventArgs object.</param>
    protected void VncClientConnectionLost(object sender, EventArgs e)
    {
        // If the remote host dies, and there are attempts to write
        // keyboard/mouse/update notifications, this may get called 
        // many times, and from main or worker thread.
        // Guard against this and invoke Disconnect once.
        if (state == RuntimeState.Connected)
        {
            SetState(RuntimeState.Disconnecting);
            Disconnect();
        }
    }

    /// <summary>
    /// Dispatches the ConnectionLost event if any targets have registered.
    /// </summary>
    /// <param name="e">An EventArgs object.</param>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is in the Connected state.</exception>
    protected void OnConnectionLost()
    {
        if (ConnectionLost != null)
        {
            ConnectionLost(this, EventArgs.Empty);
        }
    }

    /// <summary>
    /// Dispatches the ConnectComplete event if any targets have registered.
    /// </summary>
    /// <param name="e">A ConnectEventArgs object with information about the remote framebuffer's geometry.</param>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is not in the Connected state.</exception>
    protected void OnConnectComplete(ConnectEventArgs e)
    {
        if (ConnectComplete != null)
        {
            ConnectComplete(this, e);
        }
    }

    #endregion

    #region Public Methods
    /// <summary>
    /// Connect to a VNC Host and determine whether or not the server requires a password.
    /// </summary>
    /// <param name="host">The IP Address or Host Name of the VNC Host.</param>
    /// <exception cref="System.ArgumentNullException">Thrown if host is null.</exception>
    /// <exception cref="System.ArgumentOutOfRangeException">Thrown if display is negative.</exception>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is already Connected.  See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    public void Connect(string host)
    {
        // Use Display 0 by default.
        Connect(host, 0);
    }

    /// <summary>
    /// Connect to a VNC Host and determine whether or not the server requires a password.
    /// </summary>
    /// <param name="host">The IP Address or Host Name of the VNC Host.</param>
    /// <param name="viewOnly">Determines whether mouse and keyboard events will be sent to the host.</param>
    /// <exception cref="System.ArgumentNullException">Thrown if host is null.</exception>
    /// <exception cref="System.ArgumentOutOfRangeException">Thrown if display is negative.</exception>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is already Connected.  See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    public void Connect(string host, bool viewOnly)
    {
        // Use Display 0 by default.
        Connect(host, 0, viewOnly);
    }

    /// <summary>
    /// Connect to a VNC Host and determine whether or not the server requires a password.
    /// </summary>
    /// <param name="host">The IP Address or Host Name of the VNC Host.</param>
    /// <param name="display">The Display number (used on Unix hosts).</param>
    /// <exception cref="System.ArgumentNullException">Thrown if host is null.</exception>
    /// <exception cref="System.ArgumentOutOfRangeException">Thrown if display is negative.</exception>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is already Connected.  See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    public void Connect(string host, int display)
    {
        Connect(host, display, false);
    }

    /// <summary>
    /// Connect to a VNC Host and determine whether or not the server requires a password.
    /// </summary>
    /// <param name="host">The IP Address or Host Name of the VNC Host.</param>
    /// <param name="display">The Display number (used on Unix hosts).</param>
    /// <param name="viewOnly">Determines whether mouse and keyboard events will be sent to the host.</param>
    /// <exception cref="System.ArgumentNullException">Thrown if host is null.</exception>
    /// <exception cref="System.ArgumentOutOfRangeException">Thrown if display is negative.</exception>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is already Connected.  See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    public void Connect(string host, int display, bool viewOnly)
    {
        // TODO: Should this be done asynchronously so as not to block the UI?  Since an event 
        // indicates the end of the connection, maybe that would be a better design.
        InsureConnection(false);

        if (host == null) throw new ArgumentNullException("host");
        if (display < 0) throw new ArgumentOutOfRangeException("display", display, "Display number must be a positive integer.");

        // Start protocol-level handling and determine whether a password is needed
        vnc = new VncClient();
        vnc.ConnectionLost += new EventHandler(VncClientConnectionLost);

        passwordPending = vnc.Connect(host, display, VncPort, viewOnly);

        if (passwordPending)
        {
            // Server needs a password, so call which ever method is refered to by the GetPassword delegate.
            string password = _password;

            if (password == null)
            {
                // No password could be obtained (e.g., user clicked Cancel), so stop connecting
                return;
                //state = RuntimeState.Connected; // fake it so we don't crash in Disconnect()
                //Disconnect();
            }
            else
            {
                Authenticate(password);
            }
        }
        else
        {
            // No password needed, so go ahead and Initialize here
            Initialize();
        }
    }

    /// <summary>
    /// Authenticate with the VNC Host using a user supplied password.
    /// </summary>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is already Connected.  See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    /// <exception cref="System.NullReferenceException">Thrown if the password is null.</exception>
    /// <param name="password">The user's password.</param>
    public void Authenticate(string password)
    {
        InsureConnection(false);
        if (!passwordPending) throw new InvalidOperationException("Authentication is only required when Connect() returns True and the VNC Host requires a password.");
        if (password == null) throw new NullReferenceException("password");

        passwordPending = false;  // repeated calls to Authenticate should fail.
        if (vnc.Authenticate(password))
        {
            Initialize();
        }
        else
        {
            OnConnectionLost();
        }
    }

    /// <summary>
    /// Changes the input mode to view-only or interactive.
    /// </summary>
    /// <param name="viewOnly">True if view-only mode is desired (no mouse/keyboard events will be sent).</param>
    public void SetInputMode(bool viewOnly)
    {
        vnc.SetInputMode(viewOnly);
    }

    /// <summary>
    /// After protocol-level initialization and connecting is complete, the local GUI objects have to be set-up, and requests for updates to the remote host begun.
    /// </summary>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is already in the Connected state.  See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>		
    protected void Initialize()
    {
        // Finish protocol handshake with host now that authentication is done.
        InsureConnection(false);
        vnc.Initialize();
        SetState(RuntimeState.Connected);

        // Create a buffer on which updated rectangles will be drawn and draw a "please wait..." 
        // message on the buffer for initial display until we start getting rectangles
        SetupDesktop();

        // Update the scrollable region to reflect the size of the framebuffer
        //AutoScrollMinSize = new Size(vnc.Framebuffer.Width, vnc.Framebuffer.Height);

        // Tell the user of this control the necessary info about the desktop in order to setup the display
        OnConnectComplete(new ConnectEventArgs(vnc.Framebuffer.Width,
                                               vnc.Framebuffer.Height,
                                               vnc.Framebuffer.DesktopName));

        // Start getting updates from the remote host (vnc.StartUpdates will begin a worker thread).
        vnc.VncUpdate += new VncUpdateHandler(VncUpdate);
        //vnc.StartUpdates();
    }

    private void SetState(RuntimeState newState)
    {
        state = newState;

        // Set mouse pointer according to new state
        //switch (state)
        //{
        //    case RuntimeState.Connected:
        //        // Change the cursor to the "vnc" custor--a see-through dot
        //        Cursor = new Cursor(GetType(), "Resources.vnccursor.cur");
        //        break;
        //    // All other states should use the normal cursor.
        //    case RuntimeState.Disconnected:
        //    default:
        //        Cursor = Cursors.Default;
        //        break;
        //}
    }

    /// <summary>
    /// Creates and initially sets-up the local bitmap that will represent the remote desktop image.
    /// </summary>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is not already in the Connected state. See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    protected void SetupDesktop()
    {
        InsureConnection(true);

        // Create a new bitmap to cache locally the remote desktop image.  Use the geometry of the
        // remote framebuffer, and 32bpp pixel format (doesn't matter what the server is sending--8,16,
        // or 32--we always draw 32bpp here for efficiency).
        desktop = new Bitmap(vnc.Framebuffer.Width, vnc.Framebuffer.Height, PixelFormat.Format32bppPArgb);

        
        
    }

    /// <summary>
    /// Draws the given message (white text) on the local desktop (all black).
    /// </summary>
    /// <param name="message">The message to be drawn.</param>
    protected void DrawDesktopMessage(string message)
    {
        System.Diagnostics.Debug.Assert(desktop != null, "Can't draw on desktop when null.");
        // Draw the given message on the local desktop
        using (Graphics g = Graphics.FromImage(desktop))
        {
            g.FillRectangle(Brushes.Black, vnc.Framebuffer.Rectangle);

            StringFormat format = new StringFormat();
            format.Alignment = StringAlignment.Center;
            format.LineAlignment = StringAlignment.Center;

            g.DrawString(message,
                         new Font("Arial", 12),
                         new SolidBrush(Color.White),
                         new PointF(vnc.Framebuffer.Width / 2, vnc.Framebuffer.Height / 2), format);
        }

    }

    /// <summary>
    /// Stops the remote host from sending further updates and disconnects.
    /// </summary>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is not already in the Connected state. See <see cref="VncSharp.RemoteDesktop.IsConnected" />.</exception>
    public void Disconnect()
    {
        InsureConnection(true);
        vnc.ConnectionLost -= new EventHandler(VncClientConnectionLost);
        vnc.Disconnect();
        SetState(RuntimeState.Disconnected);
        OnConnectionLost();
        //Invalidate();
    }

    #endregion
    public VncWeb()
    {
       
    }
    protected override void  OnLoad(EventArgs e)
    {
 	     base.OnLoad(e);
         if (!this.Page.IsPostBack)
             Initialize();
    }
    public override void DataBind()
    {
        base.DataBind();
        
            // If the control is in design mode, draw a nice background, otherwise paint the desktop.
            if (!DesignMode) {
                switch(state) {
                    case RuntimeState.Connected:

                        DrawDesktopImage(desktop,null);
                        break;
                    case RuntimeState.Disconnected:
                        // Do nothing, just black background.
                        break;
                    default:
                        // Sanity check
                        throw new NotImplementedException(string.Format("RemoteDesktop in unknown State: {0}.", state.ToString()));
                }
            }
            else
            {
                // Draw a static screenshot of a Windows desktop to simulate the control in action
                //System.Diagnostics.Debug.Assert(designModeDesktop != null);
                DrawDesktopImage(designModeDesktop, null);
            }
		
		
    }
    /// <summary>
    /// Draws an image onto the control in a size-aware way.
    /// </summary>
    /// <param name="desktopImage">The desktop image to be drawn to the control's sufrace.</param>
    /// <param name="g">The Graphics object representing the control's drawable surface.</param>
    /// <exception cref="System.InvalidOperationException">Thrown if the RemoteDesktop control is not already in the Connected state.</exception>
    protected void DrawDesktopImage(System.Drawing.Image desktopImage, Graphics g)
    {
        // See if the image needs to be clipped (i.e., it is too big for the 
        // available space) or centered (i.e., it is too small)
        //g.DrawImage(desktopImage, x, y);
        //desktopImage.Save(HttpContext.Current.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Gif);
        if(HttpContext.Current !=null)
            HttpContext.Current.Session["img"] = desktopImage;
    }

    
}
